﻿using System;
using System.Data;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using DoNet.Utils;

namespace DoNet.TenPay
{
    public class PayApiV3
    {
        #region 基础支付
        /// <summary>
        /// 统一下单接口
        /// </summary>
        /// <param name="trade_type">交易类型 交易类型，枚举值：JSAPI：公众号支付、NATIVE：扫码支付、APP：APP支付、MICROPAY：付款码支付、MWEB：H5支付、FACEPAY：刷脸支付</param>
        /// <param name="appid">（服务商）应用ID</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="total_fee">订单总金额，单位为元。</param>
        /// <param name="description">商品描述</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="openid">用户在（服务商）appid下的唯一标识。 普通商户交易类型为JSAPI时必填</param>
        /// <param name="sub_openid">用户在子商户appid下的唯一标识。 交易类型为JSAPI时</param>
        /// <param name="sub_mchid">子商户号  服务商必填</param>
        /// <param name="sub_appid">子商户应用ID 传入</param>
        /// <param name="attach">附加数据，在查询API和支付通知中原样返回，可作为自定义参数使用，实际情况下只有支付完成状态才会返回该字段。</param>
        /// <param name="time_expire">交易结束时间
        /// 订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
        /// 订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
        /// 建议：最短失效时间间隔大于1分钟
        /// </param>
        /// <param name="goods_tag">订单优惠标记</param>
        /// <param name="scene_info">场景信息 H5必填 用户的客户端IP  H5场景信息场景类型
        /// var scene_info = new
        /// {
        ///     payer_client_ip = "14.23.150.211", //用户的客户端IP，支持IPv4和IPv6两种格式的IP地址。 示例值：14.23.150.211
        ///     h5_info = new
        ///     {
        ///         type = "Wap" //场景类型 示例值：iOS, Android, Wap
        ///     }
        /// };
        /// </param>
        /// <param name="detail">优惠功能</param>
        /// <param name="settle_info">结算信息</param>
        /// <returns></returns>
        public static TransactionsResult UnifiedOrder(string trade_type, string appid, string mchid, string apiKeyV3, string out_trade_no, string total_fee, string description, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string sub_mchid = "", string sub_appid = "", string attach = "", string time_expire = "", string goods_tag = "", object scene_info = null, object detail = null, object settle_info = null)
        {
            TransactionsResult result = null;

            string url_partner = ""; //区分服务商和商户的url
            RequestBase requestBase = new RequestBase();

            if (string.IsNullOrEmpty(appid))
            {
                Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "（服务商）应用ID appid或sp_appid必须填");
            }
            else if (string.IsNullOrEmpty(mchid))
            {
                Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "（服务商）商户号mchid或sp_mchid必须填");
            }
            else if (payMode == 0 && trade_type.ToUpper() == "JSAPI" && string.IsNullOrEmpty(openid))
            {
                Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "交易类型为JSAPI时，支付者payer中的openid必须填");
            }
            else if (payMode == 1 && string.IsNullOrEmpty(sub_mchid))
            {
                Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "服务商模式时，子商户号sub_mchid必须填");
            }
            else if (payMode == 1 && trade_type.ToUpper() == "JSAPI" && string.IsNullOrEmpty(sub_openid) && string.IsNullOrEmpty(openid))
            {
                Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "交易类型为JSAPI时，支付者payer中的sp_openid、sub_openid二选一必须填");
            }
            else if (payMode == 1 && trade_type.ToUpper() == "JSAPI" && !string.IsNullOrEmpty(sub_openid) && string.IsNullOrEmpty(sub_appid))
            {
                Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "交易类型为JSAPI时，若sub_openid有传的情况下，sub_appid必填，且sub_appid需与sub_openid对应");
            }
            else if (trade_type.ToUpper() == "MWEB" && scene_info == null)
            {
                Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "交易类型为MWEB时，场景信息必填");
            }
            else
            {
                //支付者
                object payer = null;
                if (payMode == 0)
                {
                    //应用ID
                    requestBase.setParameter("appid", appid);
                    //直连商户号
                    requestBase.setParameter("mchid", mchid);

                    if (trade_type == "JSAPI")
                    {
                        if (!string.IsNullOrEmpty(openid))
                        {
                            payer = new
                            {
                                openid = openid
                            };
                        }
                    }
                }
                else if (payMode == 1)
                {
                    //服务商模式URL跟商户模式URL不一样
                    url_partner = "partner/";

                    //服务商应用ID
                    requestBase.setParameter("sp_appid", appid);
                    //服务商户号
                    requestBase.setParameter("sp_mchid", mchid);
                    //子商户号
                    requestBase.setParameter("sub_mchid", sub_mchid);

                    if (trade_type == "JSAPI")
                    {
                        // sp_openid sub_openid 二选一
                        if (!string.IsNullOrEmpty(openid))
                        {
                            payer = new
                            {
                                sp_openid = openid
                            };
                        }
                        else
                        {
                            //子商户申请的应用ID，全局唯一。请求基础下单接口时请注意APPID的应用属性，例如公众号场景下，需使用应用属性为公众号的APPID
                            if (!string.IsNullOrEmpty(sub_openid))
                            {
                                payer = new
                                {
                                    sub_openid = sub_openid
                                };
                            }
                        }
                    }

                    //若sub_openid有传的情况下，sub_appid必填，且sub_appid需与sub_openid对应
                    if (!string.IsNullOrEmpty(sub_appid))
                    {
                        //子商户应用ID
                        requestBase.setParameter("sub_appid", sub_appid);
                    }
                }

                //订单金额
                object amount = new
                {
                    total = Convert.ToInt32(Convert.ToDecimal(total_fee) * 100)
                    //currency = 
                };
                requestBase.setParameter("amount", amount);

                //支付者
                if (payer != null)
                {
                    requestBase.setParameter("payer", payer);
                }

                //商品描述
                requestBase.setParameter("description", description);
                //商户订单号
                requestBase.setParameter("out_trade_no", out_trade_no);

                //交易结束时间
                //订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
                //订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
                //建议：最短失效时间间隔大于1分钟
                if (!string.IsNullOrEmpty(time_expire))
                {
                    requestBase.setParameter("time_expire", time_expire);
                }
                //附加数据
                if (!string.IsNullOrEmpty(attach))
                {
                    requestBase.setParameter("attach", attach);
                }

                //订单优惠标记
                if (!string.IsNullOrEmpty(goods_tag))
                {
                    requestBase.setParameter("goods_tag", goods_tag);
                }

                //结算信息
                if (settle_info != null)
                {
                    requestBase.setParameter("settle_info", settle_info);
                }

                //优惠功能
                if (detail != null)
                {
                    requestBase.setParameter("detail", detail);
                }

                //场景信息
                if (scene_info != null)
                {
                    requestBase.setParameter("scene_info", scene_info);
                }

                //Notify/Wechat/Result/V3/
                //var notify_url = WechatConfig.WebApiUrl + "Notify/Wechat/Result/V3/" + suffix;
                //Log.Error("PayApiV3—JsApiPay", "notify_url : " + notify_url);
                //通知地址
                if (!string.IsNullOrEmpty(notify_url))
                {
                    requestBase.setParameter("notify_url", notify_url);
                }

                //拼接API连接 支付方式
                var tradeType = trade_type.ToLower();
                if (tradeType == "mweb")
                {
                    tradeType = "h5";
                }

                var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/pay/{0}transactions/{1}", url_partner, tradeType));

                string postData = JsonUtil.ObjectToJson(requestBase.getAllParameters());
                ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid, "POST", postData, null, true, apiKeyV3);
                if (responseHandler.IsVerifySign)
                {
                    var errorResponseBody = responseHandler.ErrorResponseBody;
                    if (errorResponseBody != null)
                    {
                        Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", JsonUtil.ObjectToJson(errorResponseBody));
                    }
                    else
                    {
                        var responseBody = responseHandler.ResponseBody;
                        if (!string.IsNullOrEmpty(responseBody))
                        {
                            result = JsonUtil.JsonToT<TransactionsResult>(responseBody);
                        }
                    }
                }
                else
                {
                    Log.Error("Wechat-V3—PayApiV3—UnifiedOrder", "签名验证失败！");
                }
            }
            return result;
        }

        /// <summary>
        /// JSAPI下单
        /// 商户系统先调用该接口在微信支付服务后台生成预支付交易单，返回正确的预支付交易会话标识后再按Native、JSAPI、APP等不同场景生成交易串调起支付。
        /// 注意：服务商和商户的url地址不一样
        /// </summary>
        /// <param name="appid">（服务商）应用ID</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="total_fee">订单总金额，单位为元。</param>
        /// <param name="description">商品描述</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="openid">用户在（服务商）appid下的唯一标识。 普通商户交易类型为JSAPI时必填</param>
        /// <param name="sub_openid">用户在子商户appid下的唯一标识。 交易类型为JSAPI时</param>
        /// <param name="sub_mchid">子商户号  服务商必填</param>
        /// <param name="sub_appid">子商户应用ID 传入</param>
        /// <param name="attach">附加数据</param>
        /// <param name="time_expire">交易结束时间
        /// 订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
        /// 订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
        /// 建议：最短失效时间间隔大于1分钟
        /// </param>
        /// <param name="goods_tag">订单优惠标记</param>
        /// <param name="scene_info">场景信息</param>
        /// <param name="detail">优惠功能</param>
        /// <param name="settle_info">结算信息</param>
        /// <returns></returns>
        public static TransactionsResult JsApiPay(string appid, string mchid, string apiKeyV3, string out_trade_no, string total_fee, string description, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string sub_mchid = "", string sub_appid = "", string attach = "", string time_expire = "", string goods_tag = "", object scene_info = null, object detail = null, object settle_info = null)
        {
            return UnifiedOrder("JSAPI", appid, mchid, apiKeyV3, out_trade_no, total_fee, description, notify_url, payMode, openid, sub_openid, sub_mchid, sub_appid, attach, time_expire, goods_tag, scene_info, detail, settle_info);
        }

        /// <summary>
        /// APP下单API
        /// 商户系统先调用该接口在微信支付服务后台生成预支付交易单，返回正确的预支付交易会话标识后再按Native、JSAPI、APP等不同场景生成交易串调起支付。
        /// 注意：服务商和商户的url地址不一样
        /// </summary>
        /// <param name="appid">（服务商）应用ID</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="total_fee">订单总金额，单位为元。</param>
        /// <param name="description">商品描述</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_mchid">子商户号  服务商必填</param>
        /// <param name="sub_appid">子商户应用ID 传入</param>
        /// <param name="attach">附加数据</param>
        /// <param name="time_expire">交易结束时间
        /// 订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
        /// 订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
        /// 建议：最短失效时间间隔大于1分钟
        /// </param>
        /// <param name="goods_tag">订单优惠标记</param>
        /// <param name="scene_info">场景信息</param>
        /// <param name="detail">优惠功能</param>
        /// <param name="settle_info">结算信息</param>
        /// <returns></returns>
        public static TransactionsResult AppPay(string appid, string mchid, string apiKeyV3, string out_trade_no, string total_fee, string description, string notify_url, int payMode = 0, string sub_mchid = "", string sub_appid = "", string attach = "", string time_expire = "", string goods_tag = "", object scene_info = null, object detail = null, object settle_info = null)
        {
            return UnifiedOrder("APP", appid, mchid, apiKeyV3, out_trade_no, total_fee, description, notify_url, payMode, "", "", sub_mchid, sub_appid, attach, time_expire, goods_tag, scene_info, detail, settle_info);
        }

        /// <summary>
        /// H5下单
        /// 商户系统先调用该接口在微信支付服务后台生成预支付交易单，返回正确的预支付交易会话标识后再按Native、JSAPI、APP等不同场景生成交易串调起支付。
        /// 注意：服务商和商户的url地址不一样
        /// </summary>
        /// <param name="appid">（服务商）应用ID</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="total_fee">订单总金额，单位为元。</param>
        /// <param name="description">商品描述</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="scene_info">场景信息 H5必填 用户的客户端IP  H5场景信息场景类型
        /// var scene_info = new
        /// {
        ///     payer_client_ip = "14.23.150.211", //用户的客户端IP，支持IPv4和IPv6两种格式的IP地址。 示例值：14.23.150.211
        ///     h5_info = new
        ///     {
        ///         type = "Wap" //场景类型 示例值：iOS, Android, Wap
        ///     }
        /// };
        /// </param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_mchid">子商户号  服务商必填</param>
        /// <param name="sub_appid">子商户应用ID 传入</param>
        /// <param name="attach">附加数据</param>
        /// <param name="time_expire">交易结束时间
        /// 订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
        /// 订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
        /// 建议：最短失效时间间隔大于1分钟
        /// </param>
        /// <param name="goods_tag">订单优惠标记</param>
        /// <param name="detail">优惠功能</param>
        /// <param name="settle_info">结算信息</param>
        /// <returns></returns>
        public static TransactionsResult H5Pay(string appid, string mchid, string apiKeyV3, string out_trade_no, string total_fee, string description, string notify_url, object scene_info, int payMode = 0, string sub_mchid = "", string sub_appid = "", string attach = "", string time_expire = "", string goods_tag = "", object detail = null, object settle_info = null)
        {
            return UnifiedOrder("MWEB", appid, mchid, apiKeyV3, out_trade_no, total_fee, description, notify_url, payMode, "", "", sub_mchid, sub_appid, attach, time_expire, goods_tag, scene_info, detail, settle_info);
        }

        /// <summary>
        /// Native下单API
        /// 商户Native支付下单接口，微信后台系统返回链接参数code_url，商户后台系统将code_url值生成二维码图片，用户使用微信客户端扫码后发起支付。
        /// 注意：服务商和商户的url地址不一样
        /// </summary>
        /// <param name="appid">（服务商）应用ID</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="total_fee">订单总金额，单位为元。</param>
        /// <param name="description">商品描述</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_mchid">子商户号  服务商必填</param>
        /// <param name="sub_appid">子商户应用ID 传入</param>
        /// <param name="attach">附加数据</param>
        /// <param name="time_expire">交易结束时间
        /// 订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
        /// 订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
        /// 建议：最短失效时间间隔大于1分钟
        /// </param>
        /// <param name="goods_tag">订单优惠标记</param>
        /// <param name="scene_info">场景信息</param>
        /// <param name="detail">优惠功能</param>
        /// <param name="settle_info">结算信息</param>
        /// <returns></returns>
        public static TransactionsResult NativePay(string appid, string mchid, string apiKeyV3, string out_trade_no, string total_fee, string description, string notify_url, int payMode = 0, string sub_mchid = "", string sub_appid = "", string attach = "", string time_expire = "", string goods_tag = "", object scene_info = null, object detail = null, object settle_info = null)
        {
            return UnifiedOrder("NATIVE", appid, mchid, apiKeyV3, out_trade_no, total_fee, description, notify_url, payMode, "", "", sub_mchid, sub_appid, attach, time_expire, goods_tag, scene_info, detail, settle_info);
        }

        #region 付款码支付 MICROPAY  还是V2版本
        #endregion

        /// <summary>
        /// JSAPI/小程序调起支付所需的参数
        /// JSAPI调起支付API/小程序调起支付API
        /// 
        /// 小程序调起支付API
        /// 通过JSAPI下单接口获取到发起支付的必要参数prepay_id，然后使用微信支付提供的小程序方法调起小程序支付。
        /// 
        /// JSAPI调起支付API
        /// 通过JSAPI下单接口获取到发起支付的必要参数prepay_id，然后使用微信支付提供的小程序方法调起小程序支付。
        /// 注意：
        /// 1. 请确保实际支付时的请求目录与后台配置的目录一致（现在已经支持配置根目录，配置后有一定的生效时间，一般5分钟内生效），否则将无法成功唤起微信支付。配置方式详见支付授权目录(https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml#part-4)。
        /// 2. WeixinJSBridge内置对象在其他浏览器中无效。
        /// 3. JSSDK的wx.chooseWXPay唤起支付，唤起格式内容参考公众号开发指南(https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#58)。
        /// 4. JSSDK的JSSDK使用步骤说明链接地址，内容说明信息(https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#1)。
        /// 5. 唤起支付返回的res对象驼峰式是JSSDK返回的格式，而err_msg是WeixinJSBridge返回的格式，如商户使用不同的方式，此处响应的字符大小写、格式需注意。
        /// </summary>
        /// <param name="appid">（服务商）应用ID</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="total_fee">订单总金额，单位为元。</param>
        /// <param name="description">商品描述</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="openid">用户在（服务商）appid下的唯一标识。 普通商户交易类型为JSAPI时必填</param>
        /// <param name="sub_openid">用户在子商户appid下的唯一标识。 交易类型为JSAPI时</param>
        /// <param name="sub_mchid">子商户号  服务商必填</param>
        /// <param name="sub_appid">子商户应用ID 传入</param>
        /// <param name="attach">附加数据</param>
        /// <param name="time_expire">交易结束时间
        /// 订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
        /// 订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
        /// 建议：最短失效时间间隔大于1分钟
        /// </param>
        /// <param name="goods_tag">订单优惠标记</param>
        /// <param name="scene_info">场景信息</param>
        /// <param name="detail">优惠功能</param>
        /// <param name="settle_info">结算信息</param>
        /// <returns></returns>
        public static string JsapiTuneUpPay(string appid, string mchid, string apiKeyV3, string out_trade_no, string total_fee, string description, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string sub_mchid = "", string sub_appid = "", string attach = "", string time_expire = "", string goods_tag = "", object scene_info = null, object detail = null, object settle_info = null)
        {
            string result = "";
            var jsApiResult = JsApiPay(appid, mchid, apiKeyV3, out_trade_no, total_fee, description, notify_url, payMode, openid, sub_openid, sub_mchid, sub_appid, attach, time_expire, goods_tag, scene_info, detail, settle_info);
            if (jsApiResult != null)
            {
                //商户申请的公众号对应的appid，由微信支付生成，可在公众号后台查看。若下单时传了sub_appid，可为sub_appid的值。
                //商户申请的小程序对应的appid，由微信支付生成，可在小程序后台查看。若下单时候传了sub_appid,须为sub_appid的值。
                //微信开放平台审核通过的移动应用appid ，为二级商户申请的应用appid。

                // 普通商户 默认返回的appid  
                // 服务商 使用子商户对用的appid 若下单时候传了sub_appid,须为sub_appid的值。
                if (payMode == 1)
                {
                    if (!string.IsNullOrEmpty(sub_appid))
                    {
                        appid = sub_appid;
                    }
                }
                string timeStamp = TimeUtil.GenerateTimeStamp();
                string nonceStr = StringUtil.GenerateNonceStr();
                string package = "prepay_id=" + jsApiResult.prepay_id;

                string signSourceString = appid + "\n" + timeStamp + "\n" + nonceStr + "\n" + package + "\n";
                string paySign = SignatureV3.Sign(signSourceString, mchid);

                RequestBase requestBase = new RequestBase();
                requestBase.setParameter("appId", appid);
                requestBase.setParameter("timeStamp", timeStamp);
                requestBase.setParameter("nonceStr", nonceStr);
                requestBase.setParameter("package", package);
                requestBase.setParameter("signType", "RSA");
                requestBase.setParameter("paySign", paySign);

                result = JsonUtil.ObjectToJson(requestBase.getAllParameters());
            }
            return result;
        }

        /// <summary>
        /// APP调起支付所需的参数
        /// APP调起支付API
        /// 通过JSAPI下单接口获取到发起支付的必要参数prepay_id，然后使用微信支付提供的小程序方法调起小程序支付。
        /// </summary>
        /// <param name="appid">（服务商）应用ID</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="total_fee">订单总金额，单位为元。</param>
        /// <param name="description">商品描述</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_mchid">子商户号  服务商必填</param>
        /// <param name="sub_appid">子商户应用ID 传入</param>
        /// <param name="attach">附加数据</param>
        /// <param name="time_expire">交易结束时间
        /// 订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
        /// 订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
        /// 建议：最短失效时间间隔大于1分钟
        /// </param>
        /// <param name="goods_tag">订单优惠标记</param>
        /// <param name="scene_info">场景信息</param>
        /// <param name="detail">优惠功能</param>
        /// <param name="settle_info">结算信息</param>
        /// <returns></returns>
        public static string AppTuneUpPay(string appid, string mchid, string apiKeyV3, string out_trade_no, string total_fee, string description, string notify_url, int payMode = 0, string sub_mchid = "", string sub_appid = "", string attach = "", string time_expire = "", string goods_tag = "", object scene_info = null, object detail = null, object settle_info = null)
        {
            string result = "";
            var jsApiResult = AppPay(appid, mchid, apiKeyV3, out_trade_no, total_fee, description, notify_url, payMode, sub_mchid, sub_appid, attach, time_expire, goods_tag, scene_info, detail, settle_info);
            if (jsApiResult != null)
            {
                //应用ID 微信开放平台审核通过的移动应用appid ，为二级商户申请的应用appid。
                // 普通商户 默认返回的appid  
                // 服务商 二级商户申请的应用appid。
                if (payMode == 1)
                {
                    if (!string.IsNullOrEmpty(sub_appid))
                    {
                        appid = sub_appid;
                    }
                }
                string timeStamp = TimeUtil.GenerateTimeStamp();
                string nonceStr = StringUtil.GenerateNonceStr();
                string prepayid = jsApiResult.prepay_id;

                string signSourceString = appid + "\n" + timeStamp + "\n" + nonceStr + "\n" + prepayid + "\n";
                string paySign = SignatureV3.Sign(signSourceString, mchid);

                RequestBase requestBase = new RequestBase();
                requestBase.setParameter("appId", appid); //应用ID 微信开放平台审核通过的移动应用appid ，为二级商户申请的应用appid。 服务商申请的公众号或移动应用appid。
                requestBase.setParameter("partnerId", mchid); //商户号 请填写商户号mchid对应的值。
                requestBase.setParameter("prepayId", prepayid); //预支付交易会话ID 请填写商户号mchid对应的值。
                requestBase.setParameter("packageValue", "Sign=WXPay"); //订单详情扩展字符串 暂填写固定值Sign=WXPay
                requestBase.setParameter("timeStamp", timeStamp);
                requestBase.setParameter("nonceStr", nonceStr);
                requestBase.setParameter("sign", paySign);

                result = JsonUtil.ObjectToJson(requestBase.getAllParameters());
            }
            return result;
        }

        /// <summary>
        /// 查询订单API
        /// 商户可以通过查询订单接口主动查询订单状态，完成下一步的业务逻辑。查询订单状态可通过微信支付订单号或商户订单号两种方式查询
        /// </summary>
        /// <param name="transaction_id">微信支付订单号</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="sub_mchid">子商户号 服务商时必填</param>
        /// <returns></returns>
        public static QueryOrderReseult OrderQuery(string transaction_id, string out_trade_no, string mchid, string apiKeyV3, string sub_mchid = "")
        {
            QueryOrderReseult result = null;

            string url_partner = ""; //区分服务商和商户的url
            string url_params = ""; //url参数
            if (!string.IsNullOrEmpty(transaction_id))
            {
                url_params += string.Format("id/{0}", transaction_id);
            }
            else
            {
                if (!string.IsNullOrEmpty(out_trade_no))
                {
                    url_params += string.Format("out-trade-no/{0}", out_trade_no);
                }
            }

            //如果子商户号 不为空 就是服务商模式
            if (!string.IsNullOrEmpty(sub_mchid))
            {
                url_partner = "partner/";
                url_params += string.Format("?sp_mchid={0}&sub_mchid={1}", mchid, sub_mchid);
            }
            else
            {
                url_params += string.Format("?mchid={0}", mchid);
            }

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/pay/{0}transactions/{1}", url_partner, url_params));
            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid, "Get", null, null, true, apiKeyV3);
            if (responseHandler.IsVerifySign)
            {
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—OrderQuery", JsonUtil.ObjectToJson(errorResponseBody));
                }
                else
                {
                    var responseBody = responseHandler.ResponseBody;
                    if (!string.IsNullOrEmpty(responseBody))
                    {
                        result = JsonUtil.JsonToT<QueryOrderReseult>(responseBody);
                    }
                }
            }
            else
            {
                Log.Error("Wechat-V3—PayApiV3—OrderQuery", "签名验证失败！");
            }
            return result;
        }
        /// <summary>
        /// 关闭订单API
        /// 以下情况需要调用关单接口：
        /// 1、商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；
        /// 2、系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
        /// 注意：
        /// 关单没有时间限制，建议在订单生成后间隔几分钟（最短5分钟）再调用关单接口，避免出现订单状态同步不及时导致关单失败。
        /// </summary>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="sub_mchid">子商户号 服务商时必填</param>
        /// <returns></returns>
        public static bool OrderClose(string out_trade_no, string mchid, string sub_mchid = "")
        {
            var isSuccess = false;

            string url_partner = ""; //区分服务商和商户的url
            string url_params = ""; //url参数

            if (!string.IsNullOrEmpty(out_trade_no))
            {
                url_params += string.Format("out-trade-no/{0}/", out_trade_no);
            }

            RequestBase requestBase = new RequestBase();

            //如果子商户号 不为空 就是服务商模式
            if (!string.IsNullOrEmpty(sub_mchid))
            {
                url_partner = "partner/";

                requestBase.setParameter("sp_mchid", mchid);
                requestBase.setParameter("sub_mchid", sub_mchid);
            }
            else
            {
                requestBase.setParameter("mchid", mchid);
            }

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/pay/{0}transactions/{1}close", url_partner, url_params));
            string postData = JsonUtil.ObjectToJson(requestBase.getAllParameters());
            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid, "POST", postData);
            var errorResponseBody = responseHandler.ErrorResponseBody;
            if (errorResponseBody != null)
            {
                Log.Error("Wechat-V3—PayApiV3—OrderClose", JsonUtil.ObjectToJson(errorResponseBody));
            }
            else
            {
                isSuccess = responseHandler.IsResponse;
            }
            return isSuccess;
        }
        #endregion

        #region 合单支付
        /// <summary>
        /// 合单支付 统一支付
        /// </summary>
        /// <param name="trade_type"></param>
        /// <param name="combine_appid">合单商户appid</param>
        /// <param name="combine_mchid">合单商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_orders">子单信息 
        /// object sub_orders = {
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = true, subsidy_amount = 10 } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = false } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" } },
        /// }
        /// </param>
        /// <param name="openid">使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。</param>
        /// <param name="sub_openid">服务商模式下，使用某个子商户的Appid获取的对应用户Openid，是用户在该子商户Appid下的唯一标识。openid和sub_openid可以选传其中之一，如果选择传sub_openid，则必须传sub_appid。</param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <param name="scene_info">场景信息 </param>
        /// <returns></returns>
        public static TransactionsResult CombineUnifiedOrder(string trade_type, string combine_appid, string combine_mchid, string combine_out_trade_no, object sub_orders, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string time_start = "", string time_expire = "", object scene_info = null)
        {
            TransactionsResult result = null;

            RequestBase requestBase = new RequestBase();

            if (payMode == 0 && trade_type.ToUpper() == "JSAPI" && string.IsNullOrEmpty(openid))
            {
                Log.Error("Wechat-V3—PayApiV3—CombineUnifiedOrder", "交易类型为JSAPI时，支付者combine_payer_info中的openid必须填");
            }
            else if (payMode == 1 && trade_type.ToUpper() == "JSAPI" && string.IsNullOrEmpty(openid) && string.IsNullOrEmpty(sub_openid))
            {
                Log.Error("Wechat-V3—PayApiV3—CombineUnifiedOrder", "交易类型为JSAPI时，支付者combine_payer_info中的sp_openid、sub_openid二选一必须填");
            }
            else if (trade_type.ToUpper() == "MWEB" && scene_info == null)
            {
                Log.Error("Wechat-V3—PayApiV3—CombineUnifiedOrder", "交易类型为MWEB时，场景信息必填");
            }
            else
            {
                //合单商户appid
                requestBase.setParameter("combine_appid", combine_appid);
                //合单商户号
                requestBase.setParameter("combine_mchid", combine_mchid);
                //合单商户订单号
                requestBase.setParameter("combine_out_trade_no", combine_out_trade_no);
                //子单信息
                requestBase.setParameter("sub_orders", sub_orders);

                //支付者
                object combine_payer_info = null;
                if (trade_type.ToUpper() == "JSAPI" || trade_type.ToUpper() == "APP")
                {
                    if (payMode == 0)
                    {
                        if (!string.IsNullOrEmpty(openid))
                        {
                            combine_payer_info = new
                            {
                                openid = openid
                            };
                        }
                    }
                    else if (payMode == 1)
                    {
                        if (!string.IsNullOrEmpty(openid))
                        {
                            combine_payer_info = new
                            {
                                openid = openid
                            };
                        }
                        else
                        {
                            if (!string.IsNullOrEmpty(sub_openid))
                            {
                                combine_payer_info = new
                                {
                                    sub_openid = sub_openid
                                };
                            }
                        }
                    }
                }
                //支付者
                if (combine_payer_info != null)
                {
                    requestBase.setParameter("combine_payer_info", combine_payer_info);
                }

                //交易起始时间
                //订单生成时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC 8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。示例值：2019-12-31T15:59:59+08:00
                if (!string.IsNullOrEmpty(time_start))
                {
                    requestBase.setParameter("time_start", time_start);
                }

                //交易结束时间
                //订单失效时间，遵循rfc3339标准格式，格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE，yyyy-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。
                //订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 。
                //建议：最短失效时间间隔大于1分钟
                if (!string.IsNullOrEmpty(time_expire))
                {
                    requestBase.setParameter("time_expire", time_expire);
                }

                //场景信息
                if (scene_info != null)
                {
                    requestBase.setParameter("scene_info", scene_info);
                }

                //通知地址
                if (!string.IsNullOrEmpty(notify_url))
                {
                    requestBase.setParameter("notify_url", notify_url);
                }

                //拼接API连接 支付方式
                var tradeType = trade_type.ToLower();
                if (tradeType == "mweb")
                {
                    tradeType = "h5";
                }

                var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/pay/combine-transactions/{0}", tradeType));
                string postData = JsonUtil.ObjectToJson(requestBase.getAllParameters());
                ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, combine_mchid, "POST", postData);
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—CombineUnifiedOrder", JsonUtil.ObjectToJson(errorResponseBody));
                }
                else
                {
                    var responseBody = responseHandler.ResponseBody;
                    if (!string.IsNullOrEmpty(responseBody))
                    {
                        result = JsonUtil.JsonToT<TransactionsResult>(responseBody);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 合单JSAPI下单API
        /// 使用合单支付接口，用户只输入一次密码，即可完成多个订单的支付。目前最少一次可支持2笔，最多一次10笔订单进行合单支付。
        /// 注意：
        /// • 订单如果需要进行抽佣等，需要在合单中指定需要进行分账（profit_sharing为true）；指定后，交易资金进入二级商户账户，处于冻结状态，可在后续使用分账接口进行分账，利用分账完结进行资金解冻，实现抽佣和对二级商户的账期。
        /// </summary>
        /// <param name="combine_appid">合单商户appid</param>
        /// <param name="combine_mchid">合单商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_orders">子单信息 
        /// object sub_orders = {
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = true, subsidy_amount = 10 } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = false } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" } },
        /// }
        /// </param>
        /// <param name="openid">使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。</param>
        /// <param name="sub_openid">服务商模式下，使用某个子商户的Appid获取的对应用户Openid，是用户在该子商户Appid下的唯一标识。openid和sub_openid可以选传其中之一，如果选择传sub_openid，则必须传sub_appid。</param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <param name="scene_info">场景信息 </param>
        /// <returns></returns>
        public static TransactionsResult CombineJsApiPay(string combine_appid, string combine_mchid, string combine_out_trade_no, object sub_orders, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string time_start = "", string time_expire = "", object scene_info = null)
        {
            return CombineUnifiedOrder("JSAPI", combine_appid, combine_mchid, combine_out_trade_no, sub_orders, notify_url, payMode, openid, sub_openid, time_start, time_expire, scene_info);
        }

        /// <summary>
        /// 合单APP下单API
        /// 使用合单支付接口，用户只输入一次密码，即可完成多个订单的支付。目前最少一次可支持2笔，最多一次10笔订单进行合单支付。
        /// </summary>
        /// <param name="combine_appid">合单商户appid</param>
        /// <param name="combine_mchid">合单商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_orders">子单信息 
        /// object sub_orders = {
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = true, subsidy_amount = 10 } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = false } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" } },
        /// }
        /// </param>
        /// <param name="openid">使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。</param>
        /// <param name="sub_openid">服务商模式下，使用某个子商户的Appid获取的对应用户Openid，是用户在该子商户Appid下的唯一标识。openid和sub_openid可以选传其中之一，如果选择传sub_openid，则必须传sub_appid。</param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <param name="scene_info">场景信息 </param>
        /// <returns></returns>
        public static TransactionsResult CombineAppPay(string combine_appid, string combine_mchid, string combine_out_trade_no, object sub_orders, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string time_start = "", string time_expire = "", object scene_info = null)
        {
            return CombineUnifiedOrder("APP", combine_appid, combine_mchid, combine_out_trade_no, sub_orders, notify_url, payMode, openid, sub_openid, time_start, time_expire, scene_info);
        }

        /// <summary>
        /// 合单H5下单API
        /// 使用合单支付接口，用户只输入一次密码，即可完成多个订单的支付。目前最少一次可支持2笔，最多一次10笔订单进行合单支付。
        /// </summary>
        /// <param name="combine_appid">合单商户appid</param>
        /// <param name="combine_mchid">合单商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_orders">子单信息 
        /// object sub_orders = {
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = true, subsidy_amount = 10 } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = false } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" } },
        /// }
        /// </param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <param name="scene_info">场景信息 </param>
        /// <returns></returns>
        public static TransactionsResult CombineH5Pay(string combine_appid, string combine_mchid, string combine_out_trade_no, object sub_orders, string notify_url, int payMode = 0, string time_start = "", string time_expire = "", object scene_info = null)
        {
            return CombineUnifiedOrder("MWEB", combine_appid, combine_mchid, combine_out_trade_no, sub_orders, notify_url, payMode, "", "", time_start, time_expire, scene_info);
        }

        /// <summary>
        /// 合单Native下单API
        /// 使用合单支付接口，用户只输入一次密码，即可完成多个订单的支付。目前最少一次可支持2笔，最多一次10笔订单进行合单支付。
        /// </summary>
        /// <param name="combine_appid">合单商户appid</param>
        /// <param name="combine_mchid">合单商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_orders">子单信息</param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <param name="scene_info">场景信息 </param>
        /// <returns></returns>
        public static TransactionsResult CombineNativePay(string combine_appid, string combine_mchid, string combine_out_trade_no, object sub_orders, string notify_url, int payMode = 0, string time_start = "", string time_expire = "", object scene_info = null)
        {
            return CombineUnifiedOrder("NATIVE", combine_appid, combine_mchid, combine_out_trade_no, sub_orders, notify_url, payMode, "", "", time_start, time_expire, scene_info);
        }

        /// <summary>
        /// 合单支付 JSAPI/小程序调起支付所需的参数
        /// JSAPI调起支付API/小程序调起支付API
        /// 
        /// 小程序调起支付API
        /// 通过JSAPI下单接口获取到发起支付的必要参数prepay_id，然后使用微信支付提供的小程序方法调起小程序支付。
        /// 
        /// JSAPI调起支付API
        /// 通过JSAPI下单接口获取到发起支付的必要参数prepay_id，然后使用微信支付提供的小程序方法调起小程序支付。
        /// 注意：
        /// 1. 请确保实际支付时的请求目录与后台配置的目录一致（现在已经支持配置根目录，配置后有一定的生效时间，一般5分钟内生效），否则将无法成功唤起微信支付。配置方式详见支付授权目录(https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml#part-4)。
        /// 2. WeixinJSBridge内置对象在其他浏览器中无效。
        /// 3. JSSDK的wx.chooseWXPay唤起支付，唤起格式内容参考公众号开发指南(https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#58)。
        /// 4. JSSDK的JSSDK使用步骤说明链接地址，内容说明信息(https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#1)。
        /// 5. 唤起支付返回的res对象驼峰式是JSSDK返回的格式，而err_msg是WeixinJSBridge返回的格式，如商户使用不同的方式，此处响应的字符大小写、格式需注意。
        /// </summary>
        /// <param name="combine_appid">合单商户appid</param>
        /// <param name="combine_mchid">合单商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_orders">子单信息 
        /// object sub_orders = {
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = true, subsidy_amount = 10 } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = false } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" } },
        /// }
        /// </param>
        /// <param name="openid">使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。</param>
        /// <param name="sub_openid">服务商模式下，使用某个子商户的Appid获取的对应用户Openid，是用户在该子商户Appid下的唯一标识。openid和sub_openid可以选传其中之一，如果选择传sub_openid，则必须传sub_appid。</param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <param name="scene_info">场景信息 </param>
        /// <returns></returns>
        public static string CombineJsapiTuneUpPay(string combine_appid, string combine_mchid, string combine_out_trade_no, object sub_orders, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string time_start = "", string time_expire = "", object scene_info = null)
        {
            string result = "";
            var jsApiResult = CombineJsApiPay(combine_appid, combine_mchid, combine_out_trade_no, sub_orders, notify_url, payMode, openid, sub_openid, time_start, time_expire, scene_info);
            if (jsApiResult != null)
            {
                string timeStamp = TimeUtil.GenerateTimeStamp();
                string nonceStr = StringUtil.GenerateNonceStr();
                string package = "prepay_id=" + jsApiResult.prepay_id;

                string signSourceString = combine_appid + "\n" + timeStamp + "\n" + nonceStr + "\n" + package + "\n";
                string paySign = SignatureV3.Sign(signSourceString, combine_mchid);

                RequestBase requestBase = new RequestBase();
                requestBase.setParameter("appId", combine_appid);
                requestBase.setParameter("timeStamp", timeStamp);
                requestBase.setParameter("nonceStr", nonceStr);
                requestBase.setParameter("package", package);
                requestBase.setParameter("signType", "RSA");
                requestBase.setParameter("paySign", paySign);

                result = JsonUtil.ObjectToJson(requestBase.getAllParameters());
            }
            return result;
        }

        /// <summary>
        /// APP调起支付所需的参数
        /// APP调起支付API
        /// 通过JSAPI下单接口获取到发起支付的必要参数prepay_id，然后使用微信支付提供的小程序方法调起小程序支付。
        /// </summary>
        /// <param name="combine_appid">合单商户appid</param>
        /// <param name="combine_mchid">合单商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="notify_url">支付结果 通知地址</param>
        /// <param name="payMode">默认为0。 0 普通商户 1 服务商</param>
        /// <param name="sub_orders">子单信息 
        /// object sub_orders = {
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = true, subsidy_amount = 10 } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", amount = new { total_amount = 100, currency = "CNY" }, settle_info = new { profit_sharing = false } },
        ///     new { mchid = "", out_trade_no = "", attach = "", description = "", goods_tag = "", amount = new { total_amount = 100, currency = "CNY" } },
        /// }
        /// </param>
        /// <param name="openid">使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。</param>
        /// <param name="sub_openid">服务商模式下，使用某个子商户的Appid获取的对应用户Openid，是用户在该子商户Appid下的唯一标识。openid和sub_openid可以选传其中之一，如果选择传sub_openid，则必须传sub_appid。</param>
        /// <param name="time_start">交易起始时间</param>
        /// <param name="time_expire">交易结束时间</param>
        /// <param name="scene_info">场景信息 </param>
        /// <returns></returns>
        public static string CombineAppTuneUpPay(string combine_appid, string combine_mchid, string combine_out_trade_no, object sub_orders, string notify_url, int payMode = 0, string openid = "", string sub_openid = "", string time_start = "", string time_expire = "", object scene_info = null)
        {
            string result = "";
            var jsApiResult = CombineAppPay(combine_appid, combine_mchid, combine_out_trade_no, sub_orders, notify_url, payMode, openid, sub_openid, time_start, time_expire, scene_info);
            if (jsApiResult != null)
            {
                string timeStamp = TimeUtil.GenerateTimeStamp();
                string nonceStr = StringUtil.GenerateNonceStr();
                string prepayid = jsApiResult.prepay_id;

                string signSourceString = combine_appid + "\n" + timeStamp + "\n" + nonceStr + "\n" + prepayid + "\n";
                string paySign = SignatureV3.Sign(signSourceString, combine_appid);

                RequestBase requestBase = new RequestBase();
                requestBase.setParameter("appId", combine_appid); //应用ID 微信开放平台审核通过的移动应用appid ，为二级商户申请的应用appid。
                requestBase.setParameter("partnerId", combine_mchid); //商户号 请填写商户号mchid对应的值。
                requestBase.setParameter("prepayId", prepayid); //预支付交易会话ID 请填写商户号mchid对应的值。
                requestBase.setParameter("packageValue", "Sign=WXPay"); //订单详情扩展字符串 暂填写固定值Sign=WXPay
                requestBase.setParameter("timeStamp", timeStamp);
                requestBase.setParameter("nonceStr", nonceStr);
                requestBase.setParameter("sign", paySign);

                result = JsonUtil.ObjectToJson(requestBase.getAllParameters());
            }
            return result;
        }

        /// <summary>
        /// 合单查询订单API
        /// 商户通过合单查询订单API查询订单状态，完成下一步的业务逻辑。
        /// 注意：
        /// • 需要调用查询接口的情况：
        /// 1、当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知。
        /// 2、调用支付接口后，返回系统错误或未知交易状态情况。
        /// 3、调用刷卡支付API，返回USERPAYING的状态。
        /// 4、调用关单或撤销接口API之前，需确认支付状态。
        /// </summary>
        /// <param name="combine_mchid">合单发起方商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号 合单支付总订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一 。 示例值：P20150806125346</param>
        /// <returns></returns>
        public static QueryOrderReseult CombineOrderQuery(string combine_mchid, string combine_out_trade_no)
        {
            QueryOrderReseult result = null;

            string url_params = ""; //url参数
            if (!string.IsNullOrEmpty(combine_out_trade_no))
            {
                url_params += string.Format("out-trade-no/{0}", combine_out_trade_no);
            }

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format(Config.GetWechatPayUrl() + "/v3/pay/combine-transactions/{0}", url_params));
            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, combine_mchid);
            var errorResponseBody = responseHandler.ErrorResponseBody;
            if (errorResponseBody != null)
            {
                Log.Error("Wechat-V3—PayApiV3—CombineOrderQuery", JsonUtil.ObjectToJson(errorResponseBody));
            }
            else
            {
                var responseBody = responseHandler.ResponseBody;
                if (!string.IsNullOrEmpty(responseBody))
                {
                    result = JsonUtil.JsonToT<QueryOrderReseult>(responseBody);
                }
            }
            return result;
        }
        /// <summary>
        /// 合单关闭订单API
        /// 合单支付订单只能使用此合单关单api完成关单。
        /// 注意：
        /// • 合单支付关单接口不支持关闭部分子单， 关单的主单商户号、单号、子单个数、子单商户号、子单单号必须与下单时完全一致。
        /// </summary>
        /// <param name="combine_mchid">合单发起方商户号</param>
        /// <param name="combine_out_trade_no">合单商户订单号</param>
        /// <param name="sub_orders">子单信息 最多支持子单条数：10
        /// object sub_orders = {
        ///     new { mchid = "", out_trade_no = "" },
        ///     new { mchid = "", out_trade_no = "" }
        /// }
        /// </param>
        /// <returns></returns>
        public static bool CombineOrderClose(string combine_mchid, string combine_out_trade_no, object sub_orders)
        {
            var isSuccess = false;
            string url_params = ""; //url参数

            if (!string.IsNullOrEmpty(combine_out_trade_no))
            {
                url_params += string.Format("out-trade-no/{0}/", combine_out_trade_no);
            }

            RequestBase requestBase = new RequestBase();
            if (sub_orders != null)
            {
                requestBase.setParameter("sub_orders", sub_orders);
            }

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/pay/combine-transactions/{0}close", url_params));
            string postData = JsonUtil.ObjectToJson(requestBase.getAllParameters());
            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, combine_mchid, "POST", postData);
            var errorResponseBody = responseHandler.ErrorResponseBody;
            if (errorResponseBody != null)
            {
                Log.Error("Wechat-V3—PayApiV3—CombineOrderClose", JsonUtil.ObjectToJson(errorResponseBody));
            }
            else
            {
                isSuccess = responseHandler.IsResponse;
            }
            return isSuccess;
        }

        #endregion

        /// <summary>
        /// 申请退款API
        /// 当交易发生之后一年内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付金额退还给买家，微信支付将在收到退款请求并且验证成功之后，将支付款按原路退还至买家账号上。
        /// 注意：
        /// 1、交易时间超过一年的订单无法提交退款
        /// 2、微信支付退款支持单笔交易分多次退款（不超50次），多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交，请不要更换退款单号，请使用原商户退款单号
        /// 3、错误或无效请求频率限制：6qps，即每秒钟异常或错误的退款申请请求不超过6次
        /// 4、每个支付订单的部分退款次数不能超过50次
        /// 5、如果同一个用户有多笔退款，建议分不同批次进行退款，避免并发退款导致退款失败
        /// 6、申请退款接口的返回仅代表业务的受理情况，具体退款是否成功，需要通过退款查询接口获取结果
        /// 7、一个月之前的订单申请退款频率限制为：5000/min
        /// 8、同一笔订单多次退款的请求需相隔1分钟
        /// </summary>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="transaction_id">微信支付订单号</param>
        /// <param name="out_trade_no">商户订单号</param>
        /// <param name="amount">金额信息 订单金额信息
        /// object amount = new { refund = 1, from = new { new { account = "", amount = 4 },new { account = "", amount = 4 } }, total = 4, currency="CNY" }
        /// </param>
        /// <param name="notify_url">退款结果回调url地址</param>
        /// <param name="sub_mchid">子商户</param>
        /// <param name="reason">退款原因</param>
        /// <param name="funds_account">退款资金来源</param>
        /// <param name="goods_detail">退款商品 指定商品退款需要传此参数，其他场景无需传递
        /// object goods_detail = new { 
        ///     new {  merchant_goods_id="", wechatpay_goods_id="", goods_name = "", unit_price = 5, refund_amount = 5, refund_quantity = 1 }, 
        ///     new {  merchant_goods_id="", wechatpay_goods_id="", goods_name = "", unit_price = 5, refund_amount = 5, refund_quantity = 1 } 
        /// } </param>
        /// <returns></returns>
        public static QueryRefundResult Refunds(string mchid, string transaction_id, string out_trade_no, object amount, string notify_url, string sub_mchid = "", string reason = "", string funds_account = "", object goods_detail = null)
        {
            /*
            子商户号	sub_mchid	string[1, 32]	是	body子商户的商户号，由微信支付生成并下发。 示例值：1900000109
            微信支付订单号	transaction_id	string[1, 32]	二选一	body原支付交易对应的微信订单号 示例值：1217752501201407033233368018
            商户订单号	out_trade_no	string[6, 32]	body原支付交易对应的商户订单号 示例值：1217752501201407033233368018
            商户退款单号	out_refund_no	string[1, 64]	是	body商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一退款单号多次请求只退一笔。 示例值：1217752501201407033233368018
            退款原因	reason	string[1, 80]	否	body若商户传入，会在下发给用户的退款消息中体现退款原因 示例值：商品已售完
            退款资金来源	funds_account	string[1,32]	否	body若传递此参数则使用对应的资金账户退款，否则默认使用未结算资金退款（仅对老资金流商户适用） 枚举值： AVAILABLE：可用余额账户 示例值：AVAILABLE
            +金额信息	amount	object	是	body订单金额信息
            */
            //https://api.mch.weixin.qq.com/v3/refund/domestic/refunds

            QueryRefundResult result = null;

            RequestBase requestBase = new RequestBase();
            //服务商 
            if (!string.IsNullOrEmpty(sub_mchid))
            {
                requestBase.setParameter("sub_mchid", sub_mchid);
            }

            if (!string.IsNullOrEmpty(transaction_id))
            {
                requestBase.setParameter("transaction_id", transaction_id);
            }
            else
            {
                if (!string.IsNullOrEmpty(out_trade_no))
                {
                    requestBase.setParameter("out_trade_no", out_trade_no);
                }
            }
            if (amount != null)
            {
                requestBase.setParameter("amount", amount);
            }
            if (!string.IsNullOrEmpty(reason))
            {
                requestBase.setParameter("reason", reason);
            }
            if (!string.IsNullOrEmpty(funds_account))
            {
                requestBase.setParameter("funds_account", funds_account);
            }

            if (goods_detail != null)
            {
                requestBase.setParameter("goods_detail", goods_detail);
            }

            //退款结果回调url	notify_url	string[8, 256]	否	body异步接收微信支付退款结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。 如果参数中传了notify_url，则商户平台上配置的回调地址将不会生效，优先回调当前传的这个地址。 示例值：https://weixin.qq.com
            if (!string.IsNullOrEmpty(notify_url))
            {
                requestBase.setParameter("notify_url", notify_url);
            }

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), "/v3/refund/domestic/refunds");
            string postData = JsonUtil.ObjectToJson(requestBase.getAllParameters());
            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid, "POST", postData);
            var responseBody = responseHandler.ResponseBody;
            if (!string.IsNullOrEmpty(responseBody))
            {
                result = JsonUtil.JsonToT<QueryRefundResult>(responseBody);
            }
            else
            {
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—Refunds", JsonUtil.ObjectToJson(errorResponseBody));
                }
            }
            return result;
        }

        /// <summary>
        /// 查询单笔退款API
        /// 提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，建议在提交退款申请后1分钟发起查询退款状态，一般来说零钱支付的退款5分钟内到账，银行卡支付的退款1-3个工作日到账。
        /// </summary>
        /// <param name="out_refund_no">商户退款单号</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="sub_mchid">子商户号 服务商时必填</param>
        /// <returns></returns>
        public static QueryRefundResult QueryRefund(string out_refund_no, string mchid, string sub_mchid = "")
        {
            QueryRefundResult result = null;

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), "/v3/refund/domestic/refunds/");
            if (!string.IsNullOrEmpty(out_refund_no))
            {
                url += string.Format("{0}", out_refund_no);
            }

            if (!string.IsNullOrEmpty(sub_mchid))
            {
                url += string.Format("?sub_mchid={0}", sub_mchid);
            }

            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid);
            var responseBody = responseHandler.ResponseBody;
            if (!string.IsNullOrEmpty(responseBody))
            {
                result = JsonUtil.JsonToT<QueryRefundResult>(responseBody);
            }
            else
            {
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—QueryRefund", JsonUtil.ObjectToJson(errorResponseBody));
                }
            }
            return result;
        }

        /// <summary>
        /// 申请交易账单API
        /// 微信支付按天提供交易账单文件，服务商可以通过该接口获取账单文件的下载地址。文件内包含交易相关的金额、时间、营销等信息，供商户核对订单、退款、银行到账等情况。
        /// </summary>
        /// <param name="bill_date">账单日期 
        /// 格式yyyy-MM-dd 仅支持三个月内的账单下载申请。 示例值：2019-06-11</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="sub_mchid">子商户号 
        /// 商户是服务商： 
        /// ● 不填则默认返回服务商下的交易或退款数据。 
        /// ● 如需下载某个子商户下的交易或退款数据，则该字段必填。</param>
        /// <param name="bill_type">账单类型
        /// 不填则默认是ALL
        /// 枚举值： ALL：返回当日所有订单信息（不含充值退款订单） 
        /// SUCCESS：返回当日成功支付的订单（不含充值退款订单） 
        /// REFUND：返回当日退款订单（不含充值退款订单）</param>
        /// <param name="tar_type">压缩类型
        /// 不填则默认是数据流
        /// 枚举值：
        /// GZIP：返回格式为.gzip的压缩包账单</param>
        /// <returns></returns>
        public static TradeBillResult ApplyTradeBill(string bill_date, string mchid, string sub_mchid = "", string bill_type = "", string tar_type = "")
        {
            TradeBillResult result = null;

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/bill/tradebill?bill_date={0}", bill_date));
            if (!string.IsNullOrEmpty(sub_mchid))
            {
                url += string.Format("&sub_mchid={0}", sub_mchid);
            }

            if (!string.IsNullOrEmpty(bill_type))
            {
                url += string.Format("&bill_type={0}", bill_type);
            }

            if (!string.IsNullOrEmpty(tar_type))
            {
                url += string.Format("&tar_type={0}", tar_type);
            }

            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid);
            var responseBody = responseHandler.ResponseBody;
            if (!string.IsNullOrEmpty(responseBody))
            {
                result = JsonUtil.JsonToT<TradeBillResult>(responseBody);
            }
            else
            {
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—ApplyTradeBill", JsonUtil.ObjectToJson(errorResponseBody));
                }
            }
            return result;
        }

        /// <summary>
        /// 申请资金账单API
        /// 微信支付按天提供微信支付账户的资金流水账单文件，服务商可以通过该接口获取账单文件的下载地址。文件内包含该账户资金操作相关的业务单号、收支金额、记账时间等信息，供商户进行核对。
        /// </summary>
        /// <param name="bill_date">账单日期 
        /// 格式yyyy-MM-dd 仅支持三个月内的账单下载申请。 示例值：2019-06-11</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="account_type">资金账户类型
        /// 不填则默认是BASIC
        /// 枚举值：
        /// BASIC：基本账户
        /// OPERATION：运营账户
        /// FEES：手续费账户
        /// 示例值：BASIC</param>
        /// <param name="algorithm">压缩类型
        /// 枚举值：
        /// AEAD_AES_256_GCM：AEAD_AES_256_GCM加密算法
        /// 示例值：AEAD_AES_256_GCM</param>
        /// <param name="tar_type">压缩类型
        /// 不填则默认是数据流
        /// 枚举值：
        /// GZIP：返回格式为.gzip的压缩包账单</param>
        /// <returns></returns>
        public static TradeBillResult FundFlowBill(string bill_date, string mchid, string account_type, string tar_type = "")
        {
            TradeBillResult result = null;

            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/bill/fundflowbill?bill_date={0}", bill_date));

            if (!string.IsNullOrEmpty(account_type))
            {
                url += string.Format("&account_type={0}", account_type);
            }

            if (!string.IsNullOrEmpty(tar_type))
            {
                url += string.Format("&tar_type={0}", tar_type);
            }

            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid);
            var responseBody = responseHandler.ResponseBody;
            if (!string.IsNullOrEmpty(responseBody))
            {
                result = JsonUtil.JsonToT<TradeBillResult>(responseBody);
            }
            else
            {
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—FundFlowBill", JsonUtil.ObjectToJson(errorResponseBody));
                }
            }
            return result;
        }

        /// <summary>
        /// 申请单个子商户资金账单API
        /// 微信支付按天提供微信支付账户的资金流水账单文件，服务商可以通过该接口获取子商户账单文件的下载地址。文件内包含子商户资金操作相关的业务单号、收支金额、记账时间等信息，供商户进行核对。
        /// 注意：
        /// • 资金账单中的数据反映的是子商户微信账户资金变动情况；
        /// • 当日账单在次日上午9点开始生成，建议商户在上午10点以后获取；
        /// • 资金账单中涉及金额的字段单位为“元”；
        /// • 该接口只能下载三个月以内的资金流水账单。
        /// • 需提前在【服务商平台 -> 产品中心】开通“下载二级商户资金账单”产品权限。
        /// </summary>
        /// <param name="bill_date">账单日期 
        /// 格式yyyy-MM-dd 仅支持三个月内的账单下载申请。 示例值：2019-06-11</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="sub_mchid">子商户号</param>
        /// <param name="account_type">资金账户类型
        /// 不填则默认是BASIC
        /// 枚举值：
        /// BASIC：基本账户
        /// OPERATION：运营账户
        /// FEES：手续费账户
        /// 示例值：BASIC</param>
        /// <param name="tar_type">压缩类型
        /// 不填则默认是数据流
        /// 枚举值：
        /// GZIP：返回格式为.gzip的压缩包账单</param>
        /// <returns></returns>
        public static SubMerchantFundflowbillResult SubMerchantFundFlowBill(string bill_date, string mchid, string sub_mchid, string account_type, string algorithm = "AEAD_AES_256_GCM", string tar_type = null)
        {
            SubMerchantFundflowbillResult result = null;

            //"https://api.mch.weixin.qq.com/v3/bill/sub-merchant-fundflowbill?sub_mchid=19000000001&bill_date=2019-06-11&account_type=BASIC&algorithm=AEAD_AES_256_GCM&tar_type=GZIP";
            var url = DomainUtil.GetDomainName(Config.GetWechatPayUrl(), string.Format("/v3/bill/sub-merchant-fundflowbill?bill_date={0}&sub_mchid={1}&account_type={2}&algorithm={3}", bill_date, sub_mchid, account_type, algorithm));
            if (!string.IsNullOrEmpty(tar_type))
            {
                url += string.Format("&tar_type={0}", tar_type);
            }

            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid);
            var responseBody = responseHandler.ResponseBody;
            if (!string.IsNullOrEmpty(responseBody))
            {
                result = JsonUtil.JsonToT<SubMerchantFundflowbillResult>(responseBody);
            }
            else
            {
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—SubMerchantFundFlowBill", JsonUtil.ObjectToJson(errorResponseBody));
                }
            }
            return result;
        }

        /// <summary>
        /// 下载账单 Gzip压缩包文件流 
        /// </summary>
        /// <param name="url">账单下载地址</param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <returns></returns>
        public static byte[] GetDownLoadBill(string url, string mchid, string apiKeyV3)
        {
            byte[] bytes = null;

            ResponseHandlerV3 responseHandler = new ResponseHandlerV3(url, mchid, "GET", null, null, true, apiKeyV3);
            var responseBytes = responseHandler.ResponseBytes;
            if (responseBytes != null)
            {
                bytes = responseBytes;
            }
            else
            {
                var errorResponseBody = responseHandler.ErrorResponseBody;
                if (errorResponseBody != null)
                {
                    Log.Error("Wechat-V3—PayApiV3—GetDownLoadBill", JsonUtil.ObjectToJson(errorResponseBody));
                }
            }
            return bytes;
        }

        /// <summary>
        /// 将 交易账单/资金账单 文件转换成bytes
        /// </summary>
        /// <param name="url"></param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="filename">文件名称（不含后缀）</param>
        /// <param name="bill_date">账单日期 
        /// 格式yyyy-MM-dd 仅支持三个月内的账单下载申请。 示例值：2019-06-11</param>
        /// <param name="tar_type">压缩类型 GZIP/null 不是GZIP时设置为null即可</param>
        /// <param name="filePath">文件保存路径</param>
        /// <param name="isHandle">是否数据处理 去掉`</param>
        /// , string mchid, string apiKeyV3
        /// <returns></returns>
        public static byte[] GetBillBytes(string url, string mchid, string apiKeyV3, string filename, string bill_date, string tar_type, string filePath)//, bool isHandle = false
        {
            byte[] bytes = null;

            if (tar_type == "GZIP")
            {
                string filePathGzip = filePath + filename + ".gzip";

                byte[] bytesGzip = GetDownLoadBill(url, mchid, apiKeyV3);
                if (bytesGzip != null)
                {
                    //将byte写入文件
                    FileUtil.WriteFileFromBuffer(filePathGzip, bytesGzip);
                    string filePathUn = filePath + filename + ".xls";
                    //将压缩文件解压到excel中
                    ZipFileUtil.UnGzipFile(filePathGzip, filePathUn);

                    //删除Gzip压缩文件
                    File.Delete(filePathGzip);

                    //将excel文件转成bytes 后删除excel文件
                    bytes = FileUtil.FileToBytes(filePathUn, true);
                }
            }
            else
            {
                bytes = GetDownLoadBill(url, mchid, apiKeyV3);
            }

            //是否数据处理 去掉`
            //if (isHandle)
            //{
            //    bytes = BillDataHandle(url, suffix, bill_date);
            //}

            return bytes;
        }

        /// <summary>
        /// 交易账单/资金账单 数据处理 去掉`
        /// 调用方法：
        /// using (StringWriter sw = new StringWriter())
        /// {
        ///     BillDataHandle(url, suffix, bill_date, sw)
        /// }
        /// </summary>
        /// <param name="url"></param>
        /// <param name="mchid">（服务商）商户号</param>
        /// <param name="apiKeyV3">API密钥V3</param>
        /// <param name="bill_date"></param>
        /// <param name="sw"></param>
        /// <returns></returns>
        public static void BillDataHandle(string url, string mchid, string apiKeyV3, string bill_date, StringWriter sw)
        {
            try
            {
                DataTable dt = new DataTable();
                DataTable dtTotal = new DataTable();

                var bytesData = GetDownLoadBill(url, mchid, apiKeyV3);
                string billData = Encoding.UTF8.GetString(bytesData).Trim();
                if (!string.IsNullOrEmpty(billData))
                {
                    string[] shuZu = Regex.Split(billData, "\r\n", RegexOptions.IgnoreCase);
                    var count = shuZu.Length;
                    if (count > 0)
                    {
                        int i = 0;
                        foreach (var item in shuZu)
                        {
                            //最后两个是汇总数据
                            if (i < (count - 2))
                            {
                                if (i == 0)
                                {
                                    string[] titleArray = Regex.Split(item, ",", RegexOptions.IgnoreCase);
                                    foreach (var title in titleArray)
                                    {
                                        dt.Columns.Add(title);
                                    }
                                }
                                else
                                {
                                    DataRow dr = dt.NewRow();
                                    string[] dataArray = Regex.Split(item, ",", RegexOptions.IgnoreCase);

                                    if (dataArray.Length > 0)
                                    {
                                        int m = 0;
                                        foreach (var data in dataArray)
                                        {
                                            dr[m] = data.Replace("`", "");
                                            m++;
                                        }
                                        dt.Rows.Add(dr);
                                    }
                                }
                            }
                            else
                            {
                                if (i < (count - 1))
                                {
                                    string[] titleArray = Regex.Split(item, ",", RegexOptions.IgnoreCase);
                                    foreach (var title in titleArray)
                                    {
                                        dtTotal.Columns.Add(title);
                                    }
                                }
                                else
                                {
                                    DataRow dr = dtTotal.NewRow();
                                    string[] dataArray = Regex.Split(item, ",", RegexOptions.IgnoreCase);

                                    if (dataArray.Length > 0)
                                    {
                                        int m = 0;
                                        foreach (var data in dataArray)
                                        {
                                            dr[m] = data.Replace("`", "");
                                            m++;
                                        }
                                        dtTotal.Rows.Add(dr);
                                    }
                                }
                            }
                            i++;
                        }
                    }

                    //ExcelHelper.BuildExcelXmlHeader(sw);
                    //ExcelHelper.ExportStandard(bill_date, dt, dtTotal, sw);
                    //ExcelHelper.BuildExcelXmlFooter(sw);
                }
            }
            catch (Exception ex)
            {
                Log.Error("Wechat-V3—PayApiV3—BillDataHandle", ex.ToString());
            }
            finally
            {
                sw.Flush();
                sw.Close();
                sw.Dispose();
            }
        }
    }
}
